/**
 * Created by oneminute on 7/20/14.
 */

var lz = require("../utils/modules")();
var async = require("async");

/**
 * 可持久化类。是所有业务数据实体类的基类。如果某个类的实例对象需要被保存到ArangoDB中，需从此基类继承。
 *
 * @param _opts 用户自定义的属性初始值。
 * @constructor
 */
function Persistent(_opts) {
    if (typeof (lz.User) !== "function") {
        lz = require("../utils/modules")();
    }
    if (!_opts) _opts = {};
    Object.defineProperty(this, "__data", {
        value: {},
        enumerable: false
    });
    Object.defineProperty(this, "__properties", {
        value: [],
        enumerable: false
    });
    Object.defineProperty(this, "__collection", {
        value: null,
        writable: true
    });
    Object.defineProperty(this, "__aql", {
        value: null,
        enumerable: false,
        writable: true
    });
    this.__properties.pushArray([
        { name: "_id", value: _opts._id },
        { name: "_key", value: _opts._key },
        { name: "_rev", value: _opts._rev }
    ]);
    this.class = this.constructor.name;
    // 在子类使用.call调用父类构造函数时，需判断是否有__initProperties函数，
    // 若没有，则表示是子类，否则是父类直接调用。
    //__initProperties.call(this);
    /*if (this.__initProperties)
     this.__initProperties(properties);*/
}

/**
 * 保存方法。通过该方法将实例对象保存到ArangoDB中。对于实例中的属性，
 * 若为普通值属性，直接保存，若为其它Persistent类型的实体属性或是其数组，
 * 则只保存其_id及其类名，分别在数组库以aid和class命名。
 *
 * @param _opts 选项参数。
 *
 * @param _callback 回调函数。具有两个参数。
 *      function(_err *错误信息，没有错误为null*, _obj *保存后的对象，一般具有_id属性*)
 */
Persistent.prototype.save = function (_opts, _callback) {
    lz.Logger.silly("persistent:save: 调用。");
    var storeObject = {};
    var self = this;
    var opts = _opts;
    var callback = _callback;
    if (arguments.length == 1) {
        opts = {saveAll: true};
        callback = _opts;
    }
    //if (opts.saveAll) {
    var tasks = [];
    Object.getOwnPropertyNames(this).forEach(function (_prop) {
        var desc = Object.getOwnPropertyDescriptor(self, _prop);
        if (!desc.enumerable) return;
        lz.Logger.silly("persistent:save: 处理属性%s: ", _prop, self[_prop]);
        if (self[_prop] instanceof Persistent) {
            lz.Logger.silly("persistent:save: 属性为可持久化对象。");
            if (opts.saveAll) {
                tasks.push(function (_cb) {
                    self[_prop].save(function (_err, _obj) {
                        storeObject[_prop] = {aid: _obj._id, class: _obj.constructor.name};
                        self[_prop] = _obj;
                        _cb(_err);
                    });
                });
            } else {
                if (self[_prop]._id) {
                    storeObject[_prop] = {aid: self[_prop]._id, class: self[_prop].constructor.name};
                }
            }
        } else if (self[_prop] && Array.isArray(self[_prop]) && self[_prop].length > 0 && self[_prop][0] instanceof Persistent) {
            lz.Logger.silly("persistent:save: 属性为可持久化对象数组。");
            storeObject[_prop] = [];
            var arrayProp = self[_prop];
            self[_prop] = [];
            arrayProp.forEach(function (_item) {
                if (opts.saveAll) {
                    tasks.push(function (_cb) {
                        _item.save(function (_err, _obj) {
                            storeObject[_prop].push({aid: _obj._id, class: _item.constructor.name});
                            self[_prop].push(_obj);
                            _cb(_err);
                        });
                    });
                } else {
                    if (_item._id) {
                        storeObject[_prop].push({aid: _item._id, class: _item.constructor.name});
                    }
                }
            });
        } else {
            storeObject[_prop] = self[_prop];
        }
    });
    tasks.push(function (_cb) {
        lz.ArangoDB.saveObject(storeObject, self.__collection, function (_err, _obj) {
            self._id = _obj._id;
            self._key = _obj._key;
            self._rev = _obj._rev;
            _cb(_err);
        });
    });
    async.series(tasks, function (_err) {
        lz.Logger.silly("persistent:save: 保存完毕。");
        callback(_err, self);
    });
    /*} else {
     lz.ArangoDB.saveObject(self, self.__collection, function (_err, _obj) {
     self._id = _obj._id;
     self._key = _obj._key;
     self._rev = _obj._rev;
     callback(_err, self);
     });
     }*/
};

Persistent.prototype.saveProperties = function (_props, _callback) {
    if (_props) {
        for (var pname in _props) {
            this[pname] = _props[pname];
        }
    } else {
        _callback(null, null);
    }
};

/**
 * 从数据库中读取数据方法。通过该方法可以从ArangoDB数据库中读取出数据的值。
 * 普通属性值直接读取，对于Persistent类型的值会递归读取。
 *
 * @param _opts 用户提供的读取参数。
 *      具有method属性，提供以下值：
 *          byAId   按ArangoDB的_id属性获取值。
 *          byId    按业务逻辑id属性获取值。
 *          byAQL   按AQL语句属性获取值。
 *      若method属性值为byAQL，则还会在aql属性中提供AQL语句。
 *      具有loadAll属性，为true则递归读取所有为Persistent类型的子属性对象。
 * @param _callback 回调函数。具有两个参数。
 *      function(_err *错误信息，没有错误为null*, _obj *保存后的对象，一般具有_id属性*)
 */
Persistent.prototype.load2 = function (_opts, _callback) {
    lz.Logger.silly("persistent:load: 调用。");
    var self = this;

    var opts = _opts;
    var callback = _callback;
    if (arguments.length == 1) {
        opts = {
            method: "byAId",
            loadAll: true
        };
        callback = _opts;
    } else if (arguments.length == 2) {
        opts = {
            method: _opts.method || "byAId",
            loadAll: _opts.hasOwnProperty("loadAll") ? _opts.loadAll : true,
            prop: _opts.prop || null,
            value: _opts.value || null,
            aql: _opts.aql || null,
            params: _opts.params || null
        };
        callback = _callback;
    }

    switch (opts.method) {
        case "byAId":
            lz.ArangoDB.getObjectById(self._id, self.__loadObjectCallback.bind(self, opts, callback));
            break;
        case "byId":
            lz.ArangoDB.getObjectByObjectId(self.id, self.__collection,
                self.__loadObjectCallback.bind(self, opts, callback));
            break;
        case "byProp":
            var params = {
                "@collection": self.__collection,
                "prop": opts.prop,
                "value": opts.value || self[opts.prop]
            };
            lz.ArangoDB.getObjectByAQL(lz.Config.db.arango.aqls.GET_OBJECT_BY_PROPERTY,
                params, self.__loadObjectCallback.bind(self, opts, callback));
            break;
        case "byAQL":
            lz.ArangoDB.getObjectByAQL(opts.aql, opts.params, self.__loadObjectCallback.bind(self, opts, callback));
            break;
    }
};

/**
 * 通过条件，列出所有符合条件的对象。
 *
 * @param _opts
 * @param _callback
 */
Persistent.prototype.list = function (_opts, _callback) {
    lz.Logger.silly("Persistent:list: 调用。");
    var self = this;
    var opts = _opts;
    var callback = _callback;
    if (arguments.length == 1) {
        opts = {};
        callback = _opts;
    }
    function loadObjectsCallback(_err, _objects) {
        if (_objects && _objects.length > 0) {
            var tasks = [];
            var objects = [];
            _objects.forEach(function (_obj) {
                var obj = new self.constructor(_obj);
                objects.push(obj);
                tasks.push(function (_cb) {
                    obj.__loadObjectCallback(_opts, function (_err, _obj) {
                        _cb(_err);
                    }, _err, _obj);
                });
            });
            async.series(tasks, function (_err) {
                if (_err) {
                    lz.Logger.error("Perssitent:list: 发生错误: ", _err);
                    callback(lz.Config.errors.getError(lz.errors.ARANGODB_SYSTEM_ERROR, _err));
                    return;
                }
                callback(null, objects);
            });
        } else {
            _callback(_err, null);
        }
    }

    switch (opts.method) {
        case "byProp":
            var params = {
                "@collection": self.__collection,
                "prop": opts.prop,
                "value": opts.value || self[opts.prop]
            };
            lz.ArangoDB.listObjectsByAQL(lz.Config.db.arango.aqls.GET_OBJECT_BY_PROPERTY,
                params, loadObjectsCallback);
            break;
        case "byAQL":
            lz.ArangoDB.listObjectsByAQL(opts.aql, opts.params, loadObjectsCallback);
            break;
    }
};

/**
 * 删除某个符合条件的对象。
 *
 * @param _opts
 * @param _callback
 */
Persistent.prototype.delete = function (_opts, _callback) {
    var self = this;
    var opts = _opts;
    var callback = _callback;
    if (arguments.length == 1) {
        opts = {};
        callback = _opts;
    }

    var tasks = [];
    Object.getOwnPropertyNames(this).forEach(function (_prop) {
        var desc = Object.getOwnPropertyDescriptor(self, _prop);
        if (!desc.enumerable) return;
        lz.Logger.silly("persistent:delete: 处理属性%s: ", _prop, self[_prop]);
        if (self[_prop] instanceof Persistent) {
            lz.Logger.silly("persistent:delete: 属性为可持久化对象。");
            tasks.push(function (_cb) {
                self[_prop].delete(function (_err, _obj) {
                    _cb(_err);
                });
            });
        } else if (self[_prop] && self[_prop].hasOwnProperty("aid")) {
            lz.Logger.silly("persistent:delete: 属性为可持久化对象的引用。");
            var obj = new lz[self[_prop].class]({_id: self[_prop].aid});
            tasks.push(function (_cb) {
                obj.refresh(function () {
                    obj.delete(function (_err) {
                        _cb(_err);
                    });
                });
            });
        } else if (self[_prop] && self[_prop] && Array.isArray(self[_prop]) && self[_prop].length > 0) {
            lz.Logger.silly("persistent:delete: 属性为可持久化对象数组。");
            if (self[_prop][0] instanceof Persistent) {
                self[_prop].forEach(function (_item) {
                    if (_item.hasOwnProperty("aid")) {
                        var obj = new lz[_item.class]({_id: _item.aid});
                        tasks.push(function (_cb) {
                            obj.delete(function (_err) {
                                _cb(_err);
                            });
                        });
                    } else {
                        tasks.push(function (_cb) {
                            _item.delete(function (_err, _obj) {
                                _cb(_err);
                            });
                        });
                    }
                });
            } else if (self[_prop][0].hasOwnProperty("aid")) {
                self[_prop].forEach(function (_item) {
                    var item = new lz[_item.class]({_id: _item.aid});
                    tasks.push(function (_cb) {
                        item.delete(function (_err, _obj) {
                            _cb(_err);
                        });
                    });
                });
            }
        }
    });
    tasks.push(function (_cb) {
        lz.ArangoDB.deleteObjectById(self._id, function (_err) {
            _cb(_err);
        });
    });
    async.series(tasks, function (_err) {
        callback(_err);
    });
};

Persistent.prototype.load = function (_opts, _callback) {
    lz.Logger.debug("Persistent:load2: 调用。");
    var self = this;
    var opts = _opts;
    var callback = _callback;
    if (arguments.length == 1) {
        opts = {
            method: "byAId",
            loadAll: true
        };
        callback = _opts;
    } else if (arguments.length == 2) {
        opts = {
            method: _opts.method || "byAId",
            loadAll: _opts.hasOwnProperty("loadAll") ? _opts.loadAll : true,
            prop: _opts.prop || null,
            value: _opts.value || null,
            aql: _opts.aql || null,
            params: _opts.params || null
        };
        callback = _callback;
    }

    if (opts.loadAll) {
        var params = {
            "@collection": self.__collection
        };
        var aql = self.__aql;
        switch (opts.method) {
            case "byAId":
                params.prop = "_id";
                params.value = self._id;
                break;
            case "byId":
                params.prop = "id";
                params.value = self.id;
                break;
            case "byProp":
                params.prop = opts.prop;
                params.value = opts.value || self[opts.prop];
                break;
            case "byAQL":
                params = opts.params;
                aql = opts.aql;
                break;
        }
        lz.ArangoDB.getObjectByAQL(aql, params, function (_err, _obj) {
            if (_err) {
                callback(_err);
                return;
            }

            /*function parseObject(_from, _to) {
             Object.getOwnPropertyNames(_from).forEach(function (_prop) {
             var desc = Object.getOwnPropertyDescriptor(_from, _prop);
             if (!desc.enumerable) return;
             var prop = _from[_prop];
             lz.Logger.silly("persistent:load2: 处理属性%s: ", _prop);
             // TODO: 解析每一个属性
             if (prop && typeof(prop) == "object" && prop.hasOwnProperty("class")) {
             var object = new lz[prop.class]
             }
             });
             }*/

            if (_obj) {
                var object = new lz[self.class](_obj);
                self.copyFrom(object);
                callback(null, object);
            } else {
                callback(null, null);
            }
        });
    } else {
        function loadCallback (_err, _obj) {
            if (_obj) {
                self.copyFrom(_obj);
            }
            callback(_err, _obj);
        };
        switch (opts.method) {
            case "byAId":
                lz.ArangoDB.getObjectById(self._id, loadCallback);
                break;
            case "byId":
                lz.ArangoDB.getObjectByObjectId(self.id, self.__collection, loadCallback);
                break;
            case "byProp":
                var params = {
                    "@collection": self.__collection,
                    "prop": opts.prop,
                    "value": opts.value || self[opts.prop]
                };
                lz.ArangoDB.getObjectByAQL(lz.Config.db.arango.aqls.GET_OBJECT_BY_PROPERTY, params, loadCallback);
                break;
            case "byAQL":
                lz.ArangoDB.getObjectByAQL(opts.aql, opts.params, loadCallback);
                break;
        }
    }
};

Persistent.prototype.modifyProperty = function (_opts, _callback) {
    var properties = {};
    var self = this;
    for (var field in _opts.fields) {
        if (self.hasOwnProperty(field) && !(self[field] instanceof Persistent) &&
            !(self[field].hasOwnProperty("aid")) && !Array.isArray(self[field])) {
            properties[field] = _opts.fields[field];
            self[field] = _opts.fields[field];
        }
    };
    lz.ArangoDB.updateObjectProperties(properties, self.__collection, _opts.condition, function (_err) {
        if (_err) {
            lz.Logger.error("Persistent:modifyProperty: 失败:", _err);
            _callback(_err);
            return;
        }

        _callback(null, self);
    });
};

/**
 * 内部以每个属性条目加载数据的方法。
 *
 * @param _opts
 * @param _callback
 * @param _err
 * @param _obj
 * @private
 */
Persistent.prototype.__loadObjectCallback = function (_opts, _callback, _err, _obj) {
    var self = this;
    if (_err) {
        _callback(_err, null);
        return;
    }
    if (!_obj) {
        _callback(null, null);
    }
    var tasks = [];
    Object.getOwnPropertyNames(self).forEach(function (_prop) {
        var desc = Object.getOwnPropertyDescriptor(self, _prop);
        var prop = self[_prop];
        var objProp = _obj[_prop];
        lz.Logger.silly("persistent:load: 处理属性%s。", _prop);
        if (_opts.loadAll && prop instanceof Persistent) {
            lz.Logger.silly("persistent:load: 存储对象。");
            prop._id = objProp.aid;
            tasks.push(function (_cb) {
                prop.load(function (_err, _obj) {
                    _cb(_err);
                });
            });
        } else if (_opts.loadAll && objProp && objProp.length && objProp.length > 0 && objProp[0].hasOwnProperty("aid")) {
            lz.Logger.silly("persistent:load: 存储对象的数组。");
            prop.length = 0;
            objProp.forEach(function (_item) {
                var classConstructor = lz[_item.class];
                var newItem = null;
                try {
                    newItem = new classConstructor({_id: _item.aid});
                } catch (_exception) {
                    lz.Logger.warn("persistent:load: exception: ", _exception);
                }
                prop.push(newItem);
                tasks.push(function (_cb) {
                    newItem.load(function (_err, _obj) {
                        _cb(_err);
                    });
                });
            });
        } else if (objProp) {
            self[_prop] = objProp;
        }
    });
    lz.Logger.silly("persistent:load: 临时赋值self %s ", self.constructor.name, self);
    if (tasks.length > 0) {
        async.series(tasks, function (_err) {
            _callback(_err, self);
        });
    } else {
        _callback(_err, self);
    }
};

Persistent.prototype.refresh = function (_callback) {
    lz.Logger.debug("Persistent:refresh: 调用。");
    this.load({loadAll: true}, function (_err, _user) {
        if (_err) {
            _callback(lz.Config.errors.getError(lz.Config.errors.ARANGODB_SYSTEM_ERROR));
            return;
        }
        _callback(_err, _user);
    });
};

/**
 * 初始化类的所有属性。应在用户自定义子类的时候在其构造函数的内部调用此函数。
 * 但不应在除此之外的其它代码块中调用此函数。
 *
 * @param _props 属性数组。形如
 *              {
 *                  name: "",   // 有效的name值
 *                  vlaue: "",  // 有效的初值
 *                  desc: {},   // 有效的descriptor值
 *              }
 * @private
 */
var __initProperties = function () {
    var self = this;
    self.__properties.forEach(function (_prop) {
        self.__data[_prop.name] = _prop.value;
        if (_prop.desc) {
            Object.defineProperty(self, _prop.name, _prop.desc);
        } else {
            Object.defineProperty(self, _prop.name, {
                set: function (_value) {
                    self.__data[_prop.name] = _value;
                },
                get: function () {
                    return self.__data[_prop.name];
                },
                enumerable: true
            });
        }
    });
    delete self.__properties;
};

Persistent.prototype.__initProperties = __initProperties;

Persistent.prototype.getFields = function (_fields) {
    var obj = {};
    var self = this;
    if (Array.isArray(_fields)) {
        _fields.forEach(function (_field) {
            if (self.hasOwnProperty(_field)) {
                obj[_field] = self[_field];
            }
        });
    }
    return obj;
};

Persistent.prototype.copyFrom = function (_from) {
    for (var p in this) {
        if (_from.hasOwnProperty(p)) {
            this[p] = _from[p];
        }
    }
};

exports = module.exports = Persistent;
